package main

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
)

/*
	1. socket编程，下边2,3,4,5,都是单纯的从服务端来说，写代码的话，就是这套流程。
	2. 建立绑定Socket,用这个Socket建立套接字，然后bind，这个函数指定Ip,和端口。
	3. 监听请求，服务器使用listen函数监听客户端对Ip和端口的请求
	4. 连接接收，如果请求过来了，并且三次握手成功才建立链接。
	5. 处理请求与发送响应，服务端用的read函数，读取建立好的链接请求数据，处理一下，然后再通过write，把响应的请求发送给客户端那里
*/
/*
	1. 下边2,3,4,都是从客户端上来说，写代码也是这套流程而已。
、	2. 建立Socket客户端也是用的socket,函数建立套接字
	3. 建立连接,然后调用connect，这个函数，传入Ip,端口号，建立和我们指定的服务端链接。
	4. 发送请求和响应,连接建立成功后，我们这边的客户端通过Write这个函数,像服务器发送数据流，并且呢,使用read函数从服务端接收响应。
*/
/*
	1. 基于 UDP 协议的网络服务大致流程也是一样的，只是服务端和客户端之间不需要建立连接。
	2. 在这里重点提一句，Go 语言标准库对这个过程进行了抽象和封装，无论我们使用什么协议建立什么形式的连接，都只需要调用net.Dial
	函数就可以了，从而大大简化了代码的编写量，下面我们就来看看该函数的用法。
*/
// 第一个例子,看一下Dial函数原型是什么。
/*
	func Dial(network, address string) (Conn, error) {
    	var d Dialer
		return d.Dial(network, address)
}
	1. 解释一下，network 参数表示传入的网络协议（比如 tcp、udp 等），address 参数表示传入的 IP 地址或域名，而端口号是可选的，
	如果需要指定的话，以「:」的形式跟在地址或域名的后面就好了。如果连接成功，该函数返回连接对象，否则返回 error。
	2. 我们来看一下几种常见协议的调用方式。
		1). TCP连接
		conn,err:=net.Dial("tcp","192.168.1.45":80)
		2). UDP连接
		conn,err:=net.Dial("udp","192.168.1.45":8080)
		3). iCMP连接(这个是使用协议名称) // 注意一点ip4是代表的是IPv4，那我们就会猜出来,ip6就说ipv6了。
		conn,err:=net.Dial("ip4:icmp","www.baidu.com")
		4). iCMP连接(使用协议编号)详细的可以看这个网站,https://www.iana.org/
		conn, err := net.Dial("ip4:1", "10.0.0.6")
*/
/*
	目前我总结了一下,Dial()这个函数支持了哪几种网络协议，tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6,unix,unixgram,unixpacket。
	1. tcp：代表 TCP 协议，其基于的 IP 协议的版本根据参数 address 的值自适应。
	2. tcp4：代表基于 IP 协议第四版的 TCP 协议。
	3. tcp6：代表基于 IP 协议第六版的 TCP 协议。
	4. udp：代表 UDP 协议，其基于的 IP 协议的版本根据参数 address 的值自适应。
	5. udp4：代表基于 IP 协议第四版的 UDP 协议。
	6. udp6：代表基于 IP 协议第六版的 UDP 协议。
	7. unix：代表 Unix 通信域下的一种内部 socket 协议，以 SOCK_STREAM 为 socket 类型。
	8. unixgram：代表 Unix 通信域下的一种内部 socket 协议，以 SOCK_DGRAM 为 socket 类型。
	9. unixpacket：代表 Unix 通信域下的一种内部 socket 协议，以 SOCK_SEQPACKET 为 socket 类型。
	在成功建立连接后，我们就可以进行数据的发送和接收，发送数据时，使用连接对象 conn 的 Write() 方法，接收数据时使用 Read() 方法
接下来，我通过第二个示例演示下Go网络编程的实现。后续..一群人觉得我代码看不懂，我把我写的每一句代码都加上注释。
*/
func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "usage: %s host:port", os.Args[0])
		os.Exit(1)
	}
	// 从参数里边获取到主机的信息
	server := os.Args[1]
	// 建立一个网络的连接
	conn, err := net.Dial("tcp", server)
	// 连接如果有问题我们打印出来错误，把程序退出来。
	CheckError(err)
	// 使用返回的俩捏对象提供的Write方法发送一个请求出去。
	_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
	CheckError(err)
	// 使用返回的俩捏对象提供的Read方法，读取里边所有的数据。
	result, err := readFull(conn)
	CheckError(err)
	// 打印一下响应的数据
	fmt.Println(string(result))
	os.Exit(0)
}

func readFull(conn net.Conn) ([]byte, error) {
	defer conn.Close()
	result := bytes.NewBuffer(nil)
	var buf [512]byte
	for {
		n, err := conn.Read(buf[0:])
		result.Write(buf[0:n])
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}
	return result.Bytes(), nil
}

func CheckError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "错误信息是: %s", err.Error())
		os.Exit(1)
	}
}

// 启动的话请注意, go run socket.go这个代码名称是自己的 baidu.com:80   也可以把80换成http
